/*
 * Scaffold Application Framework
 *
 * Copyright (c) 2010 Sii Sp. z o.o. All rights reserved.
 */
package com.gdziarmaga.webshop.web.datatable;

import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.faces.context.FacesContext;
import javax.faces.model.SelectItem;

import org.ajax4jsf.model.DataVisitor;
import org.ajax4jsf.model.ExtendedDataModel;
import org.ajax4jsf.model.Range;
import org.ajax4jsf.model.SequenceRange;
import org.apache.commons.collections.ListUtils;
import org.richfaces.model.FilterField;
import org.springframework.beans.factory.annotation.Autowired;

import com.gdziarmaga.webshop.utils.filtering.BaseFilter;
import com.gdziarmaga.webshop.utils.filtering.GenericFilter;
import com.gdziarmaga.webshop.web.utils.SelectorUtils;
import com.google.common.collect.Ordering;

/**
 * Base table model is used to create table model for richfaces datatable.
 *
 * @author Wojciech Kochman
 * @version 1.0
 * @param <T> the object type
 */
public abstract class BaseTableDataModelImpl<T> extends ExtendedDataModel implements BaseTableModel<T> {

    /**
     * serialVersionUID.
     */
    private static final long serialVersionUID = -4188575485838003516L;
    /**
     * The size of page / number of items on one list page.
     */
    private int pageSize;
    /**
     * The number of current list page.
     */
    private int pageNo;
    /**
     * The number of list's pages.
     */
    private int pages;
    /**
     * The whole number of items.
     */
    private int itemCount;
    /**
     * List if pages used in data scroller.
     */
    private List<SelectItem> pagesList;
    /**
     * List of sorting fields.
     */
//    private List<SortField2> sortFields;
    /**
     * List of filter fields.
     */
    private List<FilterField> filterFields;
    /**
     * Current list object key.
     */
    private Object currentKey;
    /**
     * Map used to cache items on the list.
     */
    private Map<Object, BaseTableRow<T>> cachedItems;

    private Set<BaseTableRow<T>> selectedRows;

    private BaseTableRow<T> selectedRow;
    
    /**
     * Cached data range.
     */
    private SequenceRange cachedRange;
    /**
     * The {@link CommonBean}.
     */
//    @Autowired
//    private CommonBean commonBean;
    /**
     * The {@link SelectorUtils}.
     */
    @Autowired
    private SelectorUtils selectorUtils;
    
    private BaseFilter filter;

    /**
     * Gets the id on an object.
     *
     * @param object the object to get id
     * @return the id
     */
    public abstract Object getObjectId(T object);

    /**
     * Gets the List of {@link BaseTableRow}.
     *
     * @return the List of {@link BaseTableRow}
     */
    public abstract List<BaseTableRow<T>> getObjectList();

    /**
     * {@inheritDoc}
     */
    @Override
    public int getRowCount(){
        return itemCount;
    }
    
    public abstract long getTableRowCount();

    /**
     * Initialize local variables.
     */
    protected void init() {
        cachedItems = Collections.synchronizedMap(new LinkedHashMap<Object, BaseTableRow<T>>());
        selectedRows = Collections.synchronizedSet(new HashSet<BaseTableRow<T>>());
//        pageSize = commonBean.getPageSize();
        pageNo = 1;
        cachedRange = new SequenceRange(0, pageSize);
        filter = null;
        calculatePages();
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public void walk(FacesContext facesContext, DataVisitor visitor, Range range, Object argument) {
        SequenceRange sequenceRange = (SequenceRange) range;
        if (this.cachedItems.isEmpty() || !areEqualRanges(this.cachedRange, sequenceRange)) {
            cachedItems.clear();
            this.cachedRange = sequenceRange;
            List<BaseTableRow<T>> temp = getObjectList();
            if (temp != null && !temp.isEmpty()) {
                for (BaseTableRow<T> item : temp) {
                    if (!selectedRows.isEmpty() && selectedRows.contains(item)) {
                        item.setSelected(true);
                    }
                    cachedItems.put(getObjectId(item.getDomainObject()), item);
                }
            }
        }
        if (!cachedItems.isEmpty()) {
            synchronized (cachedItems) {
                for (BaseTableRow<T> item : this.cachedItems.values()) {
                    visitor.process(facesContext, getObjectId(item.getDomainObject()), argument);
                }
            }
        }
    }

    public void addSelectedItem() {
        if (selectedRow != null) {
            if (selectedRow.isSelected()) {
                selectedRows.add(selectedRow);
            } else {
                selectedRows.remove(selectedRow);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
//    public void modify(List<FilterField> filterFields, List<SortField2> sortFields) {
//        boolean sortFielsChanged = false;
//        boolean filterFielsChanged = false;
//        if (!compareSortFieldsList(this.sortFields, sortFields)) {
//            this.sortFields = sortFields;
//            sortFielsChanged = true;
//        }
//        // Probably new implementation fort filter fields comparison needed
//        if (!ListUtils.isEqualList(this.filterFields, filterFields)) {
//            this.filterFields = filterFields;
//            filterFielsChanged = true;
//        }
//        if (sortFielsChanged || filterFielsChanged) {
//            cachedItems.clear();
//        }
//    }

    /**
     * Calculates number of pages base on item count and page size.
     */
    private void calculatePages() {
        itemCount = Long.valueOf(getTableRowCount()).intValue();
        if (itemCount % pageSize > 0) {
            pages = (itemCount / pageSize) + 1;
        } else {
            pages = itemCount / pageSize;
        }
        pagesList = selectorUtils.buildNumberList(pages);
    }

    /**
     * Checks if {@link SequenceRange} are equal.
     *
     * @param range1 range to compare
     * @param range2 range to compare
     * @return true if ranges are equal, otherwise false
     */
    private boolean areEqualRanges(SequenceRange range1, SequenceRange range2) {
        if (range1 == null || range2 == null) {
            return range1 == null && range2 == null;
        } else {
            return range1.getFirstRow() == range2.getFirstRow() && range1.getRows() == range2.getRows();
        }
    }

//    /**
//     * Creates {@link SortInfo} base on sort fields.
//     *
//     * @return the {@link SortInfo}
//     */
//    protected SortInfo createSortInfo() {
//        SortInfo sortInfo = null;
//        if (sortFields != null && !sortFields.isEmpty()) {
//            sortInfo = new SortInfo();
//            for (SortField2 sf : sortFields) {
//                Ordering order = sf.getOrdering();
//                String exp = sf.getExpression().getExpressionString();
//                String fieldName = exp.substring(exp.indexOf('.') + 1, exp.indexOf('}'));
//                if (Ordering.ASCENDING.equals(order)) {
//                    sortInfo.addSortFieldAscending(fieldName);
//                } else {
//                    sortInfo.addSortFieldDescending(fieldName);
//                }
//            }
//        }
//        return sortInfo;
//
//    }
    
//    protected PageInfo createPageInfoFromTableInitData(){
//        return createPageInfo(new SequenceRange(0, pageSize));
//    }
//    
//    protected PageInfo createPageInfo(){
//        return createPageInfo(this.cachedRange);
//    }
    /**
     * Creates {@link PageInfo} base on cached {@link SequenceRange}.
     *
     * @return the {@link PageInfo}
     */
//    private PageInfo createPageInfo(SequenceRange cachedRange) {
//        PageInfo pageInfo = null;
//        int pageNumber = 0;
//        int rows = 0;
//        if (cachedRange != null) {
//            int first = cachedRange.getFirstRow();
//            rows = cachedRange.getRows();
//            pageNumber = first / rows;
//            pageInfo = new PageInfo(rows, pageNumber);
//        }
//        return pageInfo;
//    }

    /**
     * Clears local cache.
     */
    public void clearCache() {
        cachedRange = null;
        cachedItems.clear();
        setPageNo(1);
        calculatePages();
    }

    /**
     * {@inheritDoc}
     */
    public void refresh() {
        clearCache();
        
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object getRowKey() {
        return currentKey;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setRowKey(Object key) {
        this.currentKey = key;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object getRowData() {
        return cachedItems.get(currentKey);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getRowIndex() {
        return -1;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object getWrappedData() {
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isRowAvailable() {
        return (this.cachedItems.get(currentKey) != null);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setRowIndex(int rowIndex) {
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setWrappedData(Object data) {
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ExtendedDataModel getModel() {
        return this;
    }

    /**
     * Getter for pagesList.
     *
     * @return the pagesList
     */
    public List<SelectItem> getPagesList() {
        return pagesList;
    }

    /**
     * Getter for pageSize.
     *
     * @return the pageSize
     */
    public int getPageSize() {
        return pageSize;
    }

    /**
     * Setter for pageSize.
     *
     * @param pageSize the pageSize to set
     */
    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }

    /**
     * Getter for pageNo.
     *
     * @return the pageNo
     */
    public int getPageNo() {
        return pageNo;
    }

    /**
     * Setter for pageNo.
     *
     * @param pageNo the pageNo to set
     */
    public void setPageNo(int pageNo) {
        this.pageNo = pageNo;
    }

    /**
     * Getter for pages.
     *
     * @return the pages
     */
    public int getPages() {
        return pages;
    }

    /**
     * Compares to lists of {@link SortField2}.
     *
     * @param list1 the first list
     * @param list2 the second list
     * @return returns <b>true</b> if list are equals, otherwise <b>false</b>
     */
//    public boolean compareSortFieldsList(List<SortField2> list1, List<SortField2> list2) {
//        if (list1 == list2) {
//            return true;
//        }
//        if (list1 == null || list2 == null || list1.size() != list2.size()) {
//            return false;
//        }
//        Iterator<SortField2> list1Iter = list1.iterator();
//        Iterator<SortField2> list2Iter = list2.iterator();
//        SortField2 obj1 = null;
//        SortField2 obj2 = null;
//        while (list1Iter.hasNext() && list2Iter.hasNext()) {
//            obj1 = list1Iter.next();
//            obj2 = list2Iter.next();
//
//            if (!(obj1 == null ? obj2 == null : (obj1.getExpression().getExpressionString().equals(obj2.getExpression().getExpressionString()) && obj1
//                    .getOrdering().equals(obj2.getOrdering())))) {
//                return false;
//            }
//        }
//        return !(list1Iter.hasNext() || list2Iter.hasNext());
//    }

    public Set<T> getSelectedRowsData() {
        Set<T> result = new HashSet<T>();
        if (selectedRows != null && !selectedRows.isEmpty()) {
            for (BaseTableRow<T> row : selectedRows) {
                result.add(row.getDomainObject());
            }
        }
        return result;
    }

    /**
     * Getter for selectedRows.
     *
     * @return the selectedRows
     */
    public Set<BaseTableRow<T>> getSelectedRows() {
        return selectedRows;
    }

    /**
     * Setter for selectedRows.
     *
     * @param selectedRows the selectedRows to set
     */
    public void setSelectedRows(Set<BaseTableRow<T>> selectedRows) {
        this.selectedRows = selectedRows;
    }

    /**
     * Getter for selectedRow.
     *
     * @return the selectedRow
     */
    public BaseTableRow<T> getSelectedRow() {
        return selectedRow;
    }

    /**
     * Setter for selectedRow.
     *
     * @param selectedRow the selectedRow to set
     */
    public void setSelectedRow(BaseTableRow<T> selectedRow) {
        this.selectedRow = selectedRow;
    }

    /**
     * Getter for filter.
     * @return the filter
     */
    public BaseFilter getFilter() {
        return filter;
    }

    /**
     * Setter for filter.
     * @param filter the filter to set
     */
    public void setFilter(BaseFilter filter) {
        this.filter = filter;
        clearCache();
    }
}